ASP.NET MVC 控制器

本文总阅读量:
  1. 1. 对输入请求进行路由
    1. 1.1. 模拟ASP.NET MVC运行时
    2. 1.2. 应用程序路由
      1. 1.2.1. url模式与路由
      2. 1.2.2. 定义应用程序路由
      3. 1.2.3. 处理路由
      4. 1.2.4. 路由处理程序
      5. 1.2.5. 处理物理文件的请求
      6. 1.2.6. 阻止已定义的路由
  2. 2. 控制器类
    1. 2.1. 编写控制器类
    2. 2.2. 操作和http动词
    3. 2.3. 处理输入数据
      1. 2.3.1. 获取request对象中的输入数据
      2. 2.3.2. 从路由中获取输入数据
      3. 2.3.3. 利用valueprovider字典获取所有来源的值
    4. 2.4. 产生操作结果
      1. 2.4.1. actionresult构造
      2. 2.4.2. 一些派生类
      3. 2.4.3. 深入执行操作结果的机制

人么总说事件会改变一切,当实际上你必须自己动手去改变一切——Andy Warhol

对输入请求进行路由

在软件中,URI(统一资源标识符)是指通过一个位置或者名称来引用资源。

当URI通过位置来识别资源是,就叫做URL(统一定位符)

当URI通过名称标志资源时,就叫做URN(统一资源名称)

ASP.NET MVC 旨在处理更通用的URI,ASP.NET Web Forms只要处理位置感知的物理资源

模拟ASP.NET MVC运行时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void ProcessRequest(HttpContext context)
{
//map对应的参数
var segments = context.Request.Url.Segments;
var controller = segments[1].TrimEnd('/');
var action = segments[2].TrimEnd('/');
var param = segments[3].TrimEnd('/');

//通过反射实例化对应的controller
var fullname = $"{this.GetType().Namespace}.{controller}";
var controllerType = Type.GetType(fullname);
var instance = Activator.CreateInstance(controllerType);
var methodInfo =
controllerType.GetMethod(action, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var res = methodInfo.Invoke(instance, null) as string;//假设没参数

//输出
context.Response.Write(res);
}

controller类中

1
2
3
4
5
6
7
8
9
10
11
namespace _0101
{
public class HomeController
{
public string Test()
{
return "hello world";
}
}

}

这个简单的例子模拟了ASP.NET MVC使用的基本机制,处理请求的组件是控制器类,通过request的url映射到专门的控制器类进行处理

应用程序路由

url模式与路由

路由:代表URL绝对路径的模式匹配字符串,即没有协议,IP地址,端口号的url字符串

比如:http://localhost:3462/home/test ==> /home/test

路由可以是常量也可以是占位符,mvc系统默认的路由是{controller}/{action}/{id},它可以匹配如Home/Index/1子类的路由

定义应用程序路由

mvc中路由是在Global.asax下被注册

1
2
3
4
5
6
7
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}

对应的类在App_Start/RouteConfig.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}

路由集合routes添加路由一般通过MapRoute进行配置,但如果你的路由有MapRoute不支持的参数可以使用

1
2
var route = newRoute(..);
RouteTable.Routes.Add("RouteName",route);

进行添加,maproute里面的参数:

name:路由名

url:url匹配模式

defaults:该url默认值对象

http://localhost:3462/并没有输入controller/action但处理程序会匹配到默认值,导航到http://localhost:3462/Home/Index下

处理路由

ASP.NET url路由模块总是根据注册的顺序进行匹配,所以前一项设置默认值会导致后一项永远得不到匹配

路由同时可以添加约束列表,MapRoute的一个重载

1
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints);

可以利用正则表达式将不正确的url拒之门外

1
2
3
4
5
6
routes.MapRoute(
name: "Product",
url: "{controller}/{productid}/{locale}",
defaults: new { controller = "Home", action = "Index", locale = "cn-ch" },
constraints: new { productid = @"\d{4}", locale = "[a-zA-Z]{2}-[a-zA-Z]{2}" }
);

路由处理程序

路由处理程序实现了IRouteHandler的接口

1
2
3
4
public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}

RequestContext中封装了路由的相关信息,MVC框架并没有提供很对内置的路由处理程序,因为自定义路由的需求并不普遍

处理物理文件的请求

RouteConfig.cs下添加routes.RouteExistingFiles = true;

1
2
3
4
public static void RegisterRoutes(RouteCollection routes)
{
routes.RouteExistingFiles = true;
}

阻止已定义的路由

相同地方

1
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

控制器类

编写控制器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}

[NonAction]
public ActionResult About()
{
。。。
}

[ActionName("About")]
public ActionResult Qunimalegebi()
{
....
}
}

[NonAction]阻止action绑定到About方法上

[ActionName("About")]指定Qunimalegebi方法的绑定action name

操作和http动词

1
2
3
4
5
6
7
[AcceptVerbs(HttpVerbs.Get|HttpVerbs.Post)]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";

return View();
}

通过AcceptVerbs特性绑定http请求方式,httpverbs枚举类型值

1
2
3
4
5
6
7
8
9
10
public enum HttpVerbs
{
Get = 1,
Post = 2,
Put = 4,
Delete = 8,
Head = 16,
Patch = 32,
Options = 64
}

利用“|”可以响应多个请求方式

处理输入数据

获取request对象中的输入数据

request中包含form,querystring,cookie,header等数据

从路由中获取输入数据

路由和上面得一致,controller中

1
var str = RouteData.Values["locale"];

url:http://localhost:4426/home/1111/en-ed?data=11

str为en-ed,所以routedata捕获的是定义好的路由值,同时根据上述路由会匹配到第一个时直接结束,所以str2位null

1
var str2 = RouteData.Values["id"];

利用valueprovider字典获取所有来源的值

1
2
var str6 = ValueProvider.GetValue("locale").AttemptedValue;//路由的值
var str7 = ValueProvider.GetValue("data").AttemptedValue;//get值

ValueProvider.GetValue("data")会有两个属性

RawValue Object类型原始值

AttemptedValue string 强转成string类型的值

产生操作结果

controller类中大部分方法返回的都是ActionResult类型,下面就看看ActionResult类是什么

actionresult构造

1
2
3
4
5
public abstract class ActionResult
{
protected ActionResult();
public abstract void ExecuteResult(ControllerContext context);
}

actionresult类是抽象类,定义了一个方法为ExecuteResult。该方法为具体派生类执行时触发的一系列动作

一些派生类

FileResult派生

  • FilePathResult: 直接将一个文件发送给客户端

可以通过更改报文头实现直接下载,不用浏览器解析

1
2
3
4
5
6
7
public ActionResult GetFile()
{
var image = new FilePathResult("~/多云.jpg", "image/jpg");
Response.AddHeader("Content-Disposition", $"attachment;filename=\"{HttpUtility.UrlDecode("多云.jpg")}\"");

return image;
}
  • FileContentResult: 返回byte字节给客户端(比如图片)
  • FileStreamResult: 返回流

深入执行操作结果的机制

控制器中我添加了以下方法

1
2
3
4
5
public ActionResult GetScript()
{
string script = "alert('hello')";
return JavaScript(script);
}

可以看到返回的是JavaScript(script);转到controller类定义可以看到

1
2
3
4
5
6
7
protected internal virtual JavaScriptResult JavaScript(string script)
{
return new JavaScriptResult()
{
Script=script
};
}

所以JavaScript(script)不过就是controller类的一个帮助方法而已,充当JavaScriptResult的对象工厂的角色,类似的如View(),大家都知道View参数默认值是View文件夹下面控制器名文件夹下面的index,所以不传参数并不代表没有参数,JavaScriptResult实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class JavaScriptResult : ActionResult
{
public string Script{get;set;}

public override void ExecuteResult(ControllerContext context)
{
if(context == null)
throw new ArgumentNullException(nameof(context));

var response = context.HttpContext.Response;
response.ContentType = "application/x-javascript";
if(Script != null)
response.Write(Script);
}
}

可以看到JavaScriptResult里面仍然是调用的response.ContentType,response.write

注意:

  • 如果控制器没有返回ActionResult,不会抛出异常,mvc框架会将return中的任何值封装成ContentResult对象序列化返回,没有返回值会映射成EmptyResult
  • mvc中只dotNet 4.5 之后可以使用async/await语法执行异步操作响应界面